﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.IO;

namespace Genuilder
{
	public class CodeWriter
	{
		internal interface IFlushable
		{
			void Flush();
		}
		public class WriteAttributeFluent<TAttribute> : IFlushable
		{
			private string _AttributeName;
			private CodeWriter _Writer;
			public WriteAttributeFluent(CodeWriter writer, string attributeName)
			{
				_AttributeName = attributeName;
				_Writer = writer;
			}

			List<KeyValuePair<string, string>> _Properties = new List<KeyValuePair<string, string>>();
			public WriteAttributeFluent<TAttribute> WithProperty(Expression<Func<TAttribute, object>> name, string value)
			{
				_Properties.Add(new KeyValuePair<string, string>(name.GetPropertyName(), value));
				return this;
			}

			void IFlushable.Flush()
			{
				if(_Properties.Count == 0)
					_Writer.Write("[" + _AttributeName + "]");
				else
				{
					_Writer.Write("[" + _AttributeName + "(" + String.Join(",", _Properties.Select(kv => kv.Key + " = " + kv.Value)) + ")]");
				}
				_Writer.NewLine();
			}
		}

		public class CloseBracket : IDisposable
		{
			private CodeWriter codeWriter;

			public CloseBracket(CodeWriter codeWriter)
			{
				this.codeWriter = codeWriter;
			}
			#region IDisposable Members

			public void Dispose()
			{
				codeWriter.PopIndent();
				codeWriter.NewLine();
				codeWriter.Write("}");
				codeWriter.NewLine();
			}

			#endregion
		}


		private System.IO.TextWriter writer;

		public CodeWriter(System.IO.TextWriter writer)
		{
			this.writer = writer;
		}

		public CodeWriter(Stream stream)
		{
			this.writer = new StreamWriter(stream);
		}

		public WriteAttributeFluent<TAttribute> WriteAttribute<TAttribute>()
		{
			EnsureFlushed();
			return AddFlushable(new WriteAttributeFluent<TAttribute>(this, typeof(TAttribute).FullName));
		}

		IFlushable _Flusheable;
		private T AddFlushable<T>(T closeable) where T : IFlushable
		{
			_Flusheable = closeable;
			return closeable;
		}
		private void EnsureFlushed()
		{
			if(_Flusheable != null)
			{
				_Flusheable.Flush();
				_Flusheable = null;
			}
		}

		public void Flush()
		{
			EnsureFlushed();
			writer.Flush();
		}


		public IDisposable WriteNamespace(string name)
		{
			EnsureFlushed();
			Write("namespace " + name);
			NewLine();
			return WriteBrackets();
		}


		int _IndentLevel;
		public void PushIndent()
		{
			_IndentLevel++;
		}

		public IDisposable WriteClass(string className, bool @internal = false)
		{
			EnsureFlushed();
			Write((@internal ? "internal" : "public") + " class " + className);
			NewLine();
			return WriteBrackets();
		}

		public IDisposable WriteBrackets()
		{
			Write("{");
			PushIndent();
			NewLine();
			return new CloseBracket(this);
		}

		public IDisposable WriteEnum(string name)
		{
			EnsureFlushed();
			Write("public enum " + name);
			NewLine();
			return WriteBrackets();
		}
		public void WriteEnumMember(string name)
		{
			EnsureFlushed();
			Write(name + ",");
			NewLine();
		}



		public void WriteProperty(Type type, string propertyName)
		{
			EnsureFlushed();
			WriteProperty(ToString(type), propertyName);
		}

		private string ToString(Type type)
		{
			if(!type.IsGenericType)
			{
				return type.FullName;
			}
			else
			{
				if(type.GetGenericTypeDefinition() == typeof(Nullable<>))
				{
					return ToString(type.GetGenericArguments()[0]) + "?";
				}
				else
					return type.FullName.Substring(0, type.FullName.IndexOf('`')) + "<" + String.Join(",", type.GetGenericArguments().Select(t => ToString(t)).ToArray()) + ">";
			}
		}
		public void WriteProperty(string typeName, string propertyName)
		{
			EnsureFlushed();
			Write("public " + typeName + " " + propertyName + " { get; set; }");
			NewLine();
		}



		public void Write(string str)
		{
			writer.Write(str);
		}

		public void PopIndent()
		{
			_IndentLevel--;
		}

		public void NewLine()
		{
			Write("\r\n");
			for(int i = 0; i < _IndentLevel; i++)
				Write("\t");
		}

		public void WriteComment(string comment)
		{
			Write("//" + comment);
			NewLine();
		}

		public void WriteUsing(string @using)
		{
			Write("using " + @using + ";");
			NewLine();
		}
	}
}
